using System;
using System.Collections.Generic;
using Framework.Library.Core.Utils.Reflection;

namespace Framework.Library.Core.Utils.Miscellaneous
{
    public struct Range<T> : IEquatable<Range<T>> where T : struct, IComparable<T>
    {
        private readonly T _end;
        private readonly T _start;

        public Range(T start, T end)
        {
            //long startLong = start.ToInt64(CultureInfo.InvariantCulture);
            //long endLong = end.ToInt64(CultureInfo.InvariantCulture);

            //try
            //{
            //  long length = checked(endLong - startLong);
            //}
            //catch (OverflowException e)
            //{
            //  throw new Exception(string.Format("Range between {0} and {1} exceeds maximum length.", start, end), e);
            //}

            _start = start;
            _end = end;
        }

        public T Start
        {
            get { return _start; }
        }

        public T End
        {
            get { return _end; }
        }

        #region IEquatable<Range<T>> Members

        public bool Equals(Range<T> other)
        {
            return (_start.Equals(other._start) && _end.Equals(other._end));
        }

        #endregion

        public bool InRange(T value)
        {
            bool greaterOrEqualToStart = (_start.CompareTo(value) != 1);
            bool lessThanEnd = (_end.CompareTo(value) == 1);

            return (greaterOrEqualToStart && lessThanEnd);
        }

        public override string ToString()
        {
            return string.Format("Start: {0}, End: {1}", _start, _end);
        }

        public override bool Equals(object obj)
        {
            if (obj is Range<T>)
            {
                var other = (Range<T>) obj;
                return Equals(other);
            }

            return false;
        }

        public override int GetHashCode()
        {
            return _start.GetHashCode() ^ _end.GetHashCode();
        }
    }

    public static class MathUtils
    {
        public static bool InRange<T>(T value, T start, T end) where T : struct, IComparable<T>
        {
            var range = new Range<T>(start, end);
            return range.InRange(value);
        }

        public static List<Range<T>> DivideRange<T>(Range<T> range, int count) where T : struct, IComparable<T>
        {
            Func<T, T, T> add = GenericOperatorFactory<T, T, T, Range<T>>.Add;
            Func<T, T, T> subtract = GenericOperatorFactory<T, T, T, Range<T>>.Subtract;
            Func<T, T, T> divide = GenericOperatorFactory<T, T, T, Range<T>>.Divide;
            Func<T, T, T> multiply = GenericOperatorFactory<T, T, T, Range<T>>.Multiply;

            T length = subtract(range.End, range.Start);
            T dividedRangeLength = divide(length, ConvertUtils.ConvertOrCast<T>(count));
            //double remainderPerDivide = (length % (double)count) / (double)count;

            var ranges = new List<Range<T>>(count);

            for (int i = 0; i < count; i++)
            {
                //decimal extra = (remainderPerDivide * (i + 1));

                T newStart = add(multiply(dividedRangeLength, ConvertUtils.ConvertOrCast<T>(i)), range.Start);
                T newEnd = add(multiply(dividedRangeLength, ConvertUtils.ConvertOrCast<T>(i + 1)), range.Start);

                var newRange = new Range<T>(newStart, newEnd);
                ranges.Add(newRange);
            }

            return ranges;
        }

        public static char IntToHex(int n)
        {
            if (n <= 9)
            {
                return (char) (n + 48);
            }
            return (char) ((n - 10) + 97);
        }

        public static int HexToInt(char h)
        {
            if ((h >= '0') && (h <= '9'))
            {
                return (h - '0');
            }
            if ((h >= 'a') && (h <= 'f'))
            {
                return ((h - 'a') + 10);
            }
            if ((h >= 'A') && (h <= 'F'))
            {
                return ((h - 'A') + 10);
            }
            return -1;
        }
    }
}